<template>
	<div
		class="relative"
		ref="containerTarget"
		:style="{
			height: containerHeight + 'px'
		}">
		<template v-if="columnWidth && data.length">
			<div
				class="m-waterfall-item absolute duration-300"
				:style="{
					width: columnWidth + 'px',
					left: (item as any)._style?.left + 'px',
					top: (item as any)._style?.top + 'px'
				}"
				v-for="(item, index) in data"
				:key="nodeKey ? (item as any)[nodeKey] : index">
				<slot :item="item" :width="columnWidth" :index="index" />
			</div>
		</template>
	</div>
</template>

<script lang="ts" setup>
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'
import {
	getAllImg,
	getImgElements,
	getMaxHeight,
	getMinHeight,
	getMinHeightColumn,
	onComplateImgs
} from './util'
import { throttle } from 'lodash-es'
import { useElementSize } from '@vueuse/core'

const props = defineProps({
	data: {
		type: Array<any>,
		required: true
	},
	nodeKey: {
		type: String
	},
	column: {
		default: 2,
		type: Number
	},
	columnSpacing: {
		default: 10,
		type: Number
	},
	rowSpacing: {
		default: 10,
		type: Number
	},
	picturePreReading: {
		type: Boolean,
		default: true
	}
})

const containerHeight: any = ref(0)
const columnHeightObj: any = ref({})
const useColumnHeightObj = () => {
	columnHeightObj.value = {}
	for (let i = 0; i < props.column; i++) {
		columnHeightObj.value[i] = 0
	}
}

const containerTarget: any = ref(null)
const containerWidth: any = ref(0)
const containerLeft: any = ref(0)

const { width } = useElementSize(containerTarget)

const useContainerWidth = () => {
	const { paddingLeft, paddingRight } = getComputedStyle(containerTarget.value, null)
	containerLeft.value = parseFloat(paddingLeft)
	containerWidth.value =
		containerTarget.value.offsetWidth - parseFloat(paddingLeft) - parseFloat(paddingRight)
}

const columnWidth = ref(0)
const columnSpacingTotal = computed(() => {
	return (props.column - 1) * props.columnSpacing
})

const useColumnWidth = () => {
	useContainerWidth()
	columnWidth.value = (containerWidth.value - columnSpacingTotal.value) / props.column
}
onMounted(() => {
	useColumnWidth()
})

let itemHeights: any[] = []
const waitImgComplate = () => {
	itemHeights = []
	let itemElements = [...document.getElementsByClassName('m-waterfall-item')]
	const imgElements = getImgElements(itemElements)
	const allImgs = getAllImg(imgElements)
	onComplateImgs(allImgs).then(() => {
		itemElements.forEach((el: any) => {
			itemHeights.push(el.offsetHeight)
		})
		useItemLocation()
	})
}

const resizeObserver = new ResizeObserver(() => {
	throttleReset()
})

const useItemHeight = () => {
	itemHeights = []
	let itemElements = [...document.getElementsByClassName('m-waterfall-item')]
	itemElements.forEach((el: any) => {
		itemHeights.push(el.offsetHeight)
		resizeObserver.observe(el)
	})
	useItemLocation()
}

const useItemLocation = () => {
	props.data.forEach((item: any, index) => {
		if (item._style) {
			return
		}
		item._style = {}
		item._style.left = getItemLeft()
		item._style.top = getItemTop()
		increasingHeight(index)
	})

	containerHeight.value = getMaxHeight(columnHeightObj.value)
}

const getItemLeft = () => {
	const column: any = getMinHeightColumn(columnHeightObj.value)
	return column * (columnWidth.value + props.columnSpacing) + containerLeft.value
}

const getItemTop = () => {
	return getMinHeight(columnHeightObj.value)
}

const increasingHeight = (index: number) => {
	const minHeightColumn: any = getMinHeightColumn(columnHeightObj.value)
	columnHeightObj.value[minHeightColumn] += itemHeights[index] + props.rowSpacing
}

onUnmounted(() => {
	props.data.forEach((item: any) => {
		delete item._style
	})
})

watch(
	() => props.data,
	(newVal) => {
		const resetColumnHeight = newVal.every((item: any) => !item._style)
		if (resetColumnHeight) {
			useColumnHeightObj()
		}
		nextTick(() => {
			if (props.picturePreReading) {
				waitImgComplate()
			} else {
				useItemHeight()
			}
		})
	},
	{
		immediate: true,
		deep: true
	}
)

function reset() {
	setTimeout(() => {
		useColumnWidth()
		props.data.forEach((item: any) => {
			item._style = null
		})
	}, 100)
}

watch(
	() => props.column,
	() => {
		if (props.picturePreReading) {
			columnWidth.value = 0
			nextTick(reset)
		} else {
			reset()
		}
	}
)

const throttleReset = throttle(reset, 100)

watch(
	() => [width.value, props.data],
	() => throttleReset()
)

onBeforeUnmount(() => resizeObserver.disconnect())
</script>

<style lang="scss" scoped></style>
